package com.element.security.server.global;

import com.element.security.constant.SecurityParams;
import com.element.security.model.entity.AuthClientE;
import org.springframework.security.oauth2.common.exceptions.InvalidClientException;
import org.springframework.security.oauth2.common.exceptions.InvalidScopeException;
import org.springframework.security.oauth2.common.exceptions.UnsupportedGrantTypeException;
import org.springframework.security.oauth2.common.util.OAuth2Utils;
import org.springframework.security.oauth2.provider.*;
import org.springframework.util.CollectionUtils;

import java.io.Serializable;
import java.util.Collections;
import java.util.Map;
import java.util.Set;

/**
 * @auther zhangwj
 * @date 2021/6/23 下午2:03
 */
public class GlobalOAuth2RequestFactory implements OAuth2RequestFactory {

    private final ClientDetailsService clientDetailsService;

    public GlobalOAuth2RequestFactory(ClientDetailsService clientDetailsService) {
        this.clientDetailsService = clientDetailsService;
    }

    public AuthorizationRequest createAuthorizationRequest(Map<String, String> authorizationParameters) {
        String clientId = authorizationParameters.get(SecurityParams.CLIENT_ID);
        String state = authorizationParameters.get(SecurityParams.STATE);
        // authorizationParameters.remove(SecurityParams.REDIRECT_URL);
        Set<String> responseTypes = OAuth2Utils.parseParameterList(authorizationParameters.get(SecurityParams.RESPONSE_TYPE));
        Set<String> scopes = this.extractScopes(authorizationParameters, clientId);
        ClientDetails clientDetails = this.clientDetailsService.loadClientByClientId(clientId);
        boolean approved = false;
        if (clientDetails instanceof AuthClientE) {
            AuthClientE cloudAuthClient = ((AuthClientE) clientDetails);
            approved = cloudAuthClient.isAutoApprove(cloudAuthClient.getAutoApprove());
        }
        AuthorizationRequest request = new AuthorizationRequest(authorizationParameters, Collections.emptyMap(), clientId,
                scopes, null, null, approved, state, null, responseTypes);
        request.setResourceIdsAndAuthoritiesFromClientDetails(clientDetails);
        return request;
    }

    public OAuth2Request createOAuth2Request(AuthorizationRequest request) {
        return request.createOAuth2Request();
    }

    public TokenRequest createTokenRequest(Map<String, String> requestParameters, ClientDetails authenticatedClient) {
        String clientId = requestParameters.get(SecurityParams.CLIENT_ID);
        if (clientId == null) {
            clientId = authenticatedClient.getClientId();
        } else if (!clientId.equals(authenticatedClient.getClientId())) {
            throw new InvalidClientException("Given client ID does not match authenticated client");
        }
        String grantType = requestParameters.get(SecurityParams.GRANT_TYPE);
        if (CollectionUtils.isEmpty(authenticatedClient.getAuthorizedGrantTypes())) {
            throw new UnsupportedGrantTypeException("无授权类型");
        }
        if (!authenticatedClient.getAuthorizedGrantTypes().contains(grantType)) {
            throw new UnsupportedGrantTypeException("无授权类型");
        }
        Set<String> scopes = this.extractScopes(requestParameters, clientId);
        return new TokenRequest(requestParameters, clientId, scopes, grantType);
    }

    public TokenRequest createTokenRequest(AuthorizationRequest authorizationRequest, String grantType) {
        return new TokenRequest(authorizationRequest.getRequestParameters(), authorizationRequest.getClientId(), authorizationRequest.getScope(), grantType);
    }

    public OAuth2Request createOAuth2Request(ClientDetails client, TokenRequest tokenRequest) {
        OAuth2Request oAuth2Request = tokenRequest.createOAuth2Request(client);
        Map<String, Object> map = client.getAdditionalInformation();
        if (!CollectionUtils.isEmpty(map)) {
            map.forEach((key, value) -> oAuth2Request.getExtensions().put(key, (Serializable) value));
        }
        return oAuth2Request;
    }

    private Set<String> extractScopes(Map<String, String> requestParameters, String clientId) {
        Set<String> scopes = OAuth2Utils.parseParameterList(requestParameters.get(SecurityParams.SCOPE));
        ClientDetails clientDetails = this.clientDetailsService.loadClientByClientId(clientId);
        if (clientDetails.isScoped()) {
            this.validateScope(scopes, clientDetails.getScope());
            return scopes;
        } else {
            scopes = clientDetails.getScope();
        }
        return scopes;
    }

    private void validateScope(Set<String> requestScopes, Set<String> clientScopes) {
        if (clientScopes != null && !clientScopes.isEmpty()) {
            for (String scope : requestScopes) {
                if (!clientScopes.contains(scope)) {
                    throw new InvalidScopeException("Invalid scope: " + scope, clientScopes);
                }
            }
        } else {
            throw new InvalidScopeException("无法访问该领域请求");
        }
        if (requestScopes.isEmpty()) {
            throw new InvalidScopeException("请求领域为空");
        }
    }
}
